/*
 * Firmware for GSC count on Arduino in combination with XPORT
 *
 * My connections between Arduino-XPORT (via defines):
 *	# TX - pin4 - red
 * 	# RX - pin5 - blue
 *  # RESET - pin 6 - yellow
 *	# GND - white
 *
 * Note: It is also possible to allow incoming telnet connections
 *       just use the IP number and the fixed telnet port 10001
 *          e.g.: telnet 192.168.123.102 10001
 */

#include "TimerOne.h"
#include <NewSoftSerial.h>

#define GSC_INTERRUPT_MODE  RISING  /* for GND ref, pulled to VDC when triggered */
#define GSC_INTERRUPT_PIN   0       /* 0 = digital pin2, 1 = digital pin 3 */
#define GSC_INTERRUPT_PINNB 2       /* pin nb */
#define LED_PIN             13      /* default LED pin */
#define TIMER_IN_SECONDS    15*60*20   /* 15 minute intervals */
#define ERROR_COUNT_HTTP_OUTGOING 30*20 /* 30 sec timout for http outgoing */
#define ERROR_COUNT_HTTP_INCOMING 10*20 /* 30 sec timout for http incoming */

#define GAS_INTERRUPT_PINNB 3
#define XPORT_SERIAL_TX_PIN 4
#define XPORT_SERIAL_RX_PIN 5
#define XPORT_RESET_PIN 6

#define ENABLE_ETHERNET_MODE


#define MAX_LOG_DAYS 32
#define INTERVALS_WITH_NO_PULSE_COUNT_BEFORE_NEW_DAY 16 /* Number of times TIMER_IN_SECONDS sec interval with no PulseCount before new day is set */
#define NB_INTERVALS_IN_DAY 96 /* Number of intervals in a day, in this case there are 96 15min intervals in 24h */
#define PRINT_DAYS_YIELD  32  /* Number of days for which to print the yield */

#define PULSE_COUNT_CONFIRM  2  /* number of 50ms frames before confirming gsc pulse */

static unsigned long PulseCount                = 0;
static unsigned long PulseCount_prev           = 0;
static unsigned long PulseCount_diff           = 0;
static unsigned long PulseCount_did_not_work   = 0;
static int           IntervalsWithNoPulseCount = -NB_INTERVALS_IN_DAY;

static unsigned long SecondCount = 0;

static long OutgoingCount = 0;
static long IncomingCount = 0;

static bool led_state        = false;
static unsigned char  led_count        = 0;
static bool add_to_day_count = false;
static bool confirm_pulse    = false;
static int  confirm_pulse_count = 0;

#ifdef ENABLE_ETHERNET_MODE
  NewSoftSerial Xport(XPORT_SERIAL_RX_PIN,XPORT_SERIAL_TX_PIN);
  enum HttpStatusEnum { HTTP_IDLE, HTTP_CONNECT, HTTP_WAIT_FOR_ACK, HTTP_TRANSFER, HTTP_DISCONNECTED };
  enum HttpStatusEnum HttpStatus;
  int input = 0;
#define RETURNSTR_LENGTH 64
  char ReturnStr[RETURNSTR_LENGTH];
  int  ReturnStrCounter;
  bool string_start;
  enum HttpMessageEnum { HTTP_GET_TIME, HTTP_SEND_DAY_VALUE, HTTP_INTERMED_VALUE, HTTP_TEST_POST };
  enum HttpMessageEnum HttpMessage;
  enum XportStatusEnum { XPORT_IDLE, XPORT_RESET, XPORT_OUTGOING, XPORT_WAIT_FOR_INCOMING, XPORT_INCOMING };
  enum XportStatusEnum XportStatus;

void xportReset()
{
  pinMode(XPORT_RESET_PIN, OUTPUT);
  digitalWrite(XPORT_RESET_PIN, LOW);
  delay(100);
  digitalWrite(XPORT_RESET_PIN, HIGH);
  delay(3000);
  XportStatus = XPORT_IDLE;
}

void xportClear()
{
  Xport.flush();
  // Read every remaining data from Xport
  while(Xport.available()){
    input = Xport.read();
  }
}

/* HTTP transfer, driven by HttpMessage enumerated type */
int HttpTransfer(int value = 0)                     
{
  OutgoingCount = ERROR_COUNT_HTTP_OUTGOING;
  XportStatus = XPORT_OUTGOING;
  bool PrintReturnString = false;
  do{
	switch(HttpStatus){
	case HTTP_IDLE:
		HttpStatus = HTTP_CONNECT;
	case HTTP_CONNECT:
		//Serial.print("\n\t ### Send the command for connection (C67.220.217.230/80) -> going to HTTP_WAIT_FOR_ACK");
		Xport.print("C67.220.217.230/80\n");
		HttpStatus = HTTP_WAIT_FOR_ACK;
		break;
	case HTTP_WAIT_FOR_ACK:
		if(Xport.available()){
			if( (char)Xport.read() == 'C'){
                          switch(HttpMessage){
                          case HTTP_GET_TIME:
				//Serial.print("\n\t ### Send the GET request (GET /time.php HTTP/1.1\nHost: shredcat.zxq.net\n\n) -> going to HTTP_TRANSFER");
		    		Xport.print("GET /my_php/time.php?val=");
				Xport.print(value);
				Xport.print(" HTTP/1.1\nHost: shredcat.zxq.net\n\n");
                                PrintReturnString = true;
                                break;
                          case HTTP_INTERMED_VALUE:
				//Serial.print("\n\t ### Send the GET request -> going to HTTP_TRANSFER");
		    		Xport.print("GET /my_php/add_gsc_value.php?val=");
				Xport.print(value);
				Xport.print(" HTTP/1.1\nHost: shredcat.zxq.net\n\n");
                                break;
                          case HTTP_TEST_POST:
				//Serial.print("\n\t ### Send the GET request (GET /time.php HTTP/1.1\nHost: shredcat.zxq.net\n\n) -> going to HTTP_TRANSFER");
		    		Xport.print("GET /my_php/post_result.php?nr=");
				Xport.print(value);
		    		Xport.print("&val=");
				Xport.print(value);
				Xport.print(" HTTP/1.1\nHost: shredcat.zxq.net\n\n");
                                PrintReturnString = true;
                                break;
                          }   
  			  HttpStatus = HTTP_TRANSFER;
			}else{
				HttpStatus = HTTP_IDLE;
			}
		}
		break;
	case HTTP_TRANSFER:
		if(Xport.available()){
			input = (char)Xport.read();
                        switch(input){
                        case '[':
				// Found start of string
				string_start = true;
                                break;
                        case ']':
				HttpStatus = HTTP_DISCONNECTED;
				string_start = false;
				break;
			default:
				if(string_start){
					ReturnStr[ReturnStrCounter++] = input;
					if(ReturnStrCounter == RETURNSTR_LENGTH){
						HttpStatus = HTTP_DISCONNECTED;
						string_start = false;
					}
				}
                                break;
			}
		}
		break;
	case HTTP_DISCONNECTED:
                Xport.flush(); 
		// Print the string:
                if(PrintReturnString){
  		  Serial.print("\n --> Answer from server: ");
		  for(int i=0;i<ReturnStrCounter;i++){
			Serial.print((char)ReturnStr[i]);
		  }
                }

		// Read every remaining data from Xport
		while(Xport.available()){
			input = Xport.read();
		}
                Xport.print('D');
                Xport.flush();                
		XportStatus = XPORT_IDLE;
		break;
	}
  }while(XportStatus == XPORT_OUTGOING);
  
  // Reset Xport when timeout
  bool error_tmp = false;
  if(XportStatus == XPORT_RESET){
    error_tmp = true;
  }

  // Reset loop vars
  HttpStatus = HTTP_IDLE;
  ReturnStrCounter = 0;
  string_start = false;
  
  if(error_tmp)
    return 0;
  return 1;
}
#endif


void setup()
{
  pinMode(LED_PIN, OUTPUT);
  pinMode(GSC_INTERRUPT_PINNB, INPUT);
  attachInterrupt(GSC_INTERRUPT_PIN, gsc_count_debounce, GSC_INTERRUPT_MODE);
  
  Serial.begin(57600); 

  digitalWrite(LED_PIN, true); // disable the LED

  Timer1.initialize();     // initialize timer1 and set period every second
  Timer1.attachInterrupt(timer_expires,50000);  // attaches function as a timer overflow interrupt

#ifdef ENABLE_ETHERNET_MODE
  Xport.begin(1200);
  xportClear();
  xportReset();
  xportClear();
#endif

  print_menu();
}


void loop()
{
  /* DRIVING THE LED */
  led_state = (led_count & 0x10) >>4;
  digitalWrite(LED_PIN, led_state); // drive the LED
  
  // Process commands when available:
  if(Serial.available()){
    process_char(Serial.read());
  }
  
  /* ADD VALUE TO DAY COUNT */
  if(add_to_day_count & (XportStatus == XPORT_IDLE)){

#ifdef ENABLE_ETHERNET_MODE
    HttpMessage = HTTP_INTERMED_VALUE;
    if(HttpTransfer(PulseCount_diff+PulseCount_did_not_work)){
	PulseCount_did_not_work = 0;
    }else{
	PulseCount_did_not_work += PulseCount_diff;
    }
#endif

    add_to_day_count = false;

  }

  if(Xport.available() && (XportStatus == XPORT_IDLE)){
    if( (char)Xport.read() == 'C'){
      IncomingCount = ERROR_COUNT_HTTP_INCOMING;
      XportStatus = XPORT_WAIT_FOR_INCOMING;
    }
  }

  if(Xport.available() && (XportStatus == XPORT_WAIT_FOR_INCOMING)){
    if( (char)Xport.read() == 'I'){
      XportStatus = XPORT_INCOMING;
    }
  }
  
  if(Xport.available() && (XportStatus == XPORT_INCOMING)){
    // Process the command which was incoming
    input = (char)Xport.read();
    switch(input){
    case 'D':
	// Return debug info
        // TBD
        Xport.print("Xport debug info\r\n=================\r\n");
        Xport.print(" * Second count :"); Xport.print(SecondCount); Xport.print("\r\n");
        Xport.print(" * Pulse count  :"); Xport.print(PulseCount);  Xport.print("\r\n");
        XportStatus = XPORT_INCOMING;
        break;
    case '?':
        // TBD
        Xport.print("Xport unknown command\r\n");
        Xport.print("List of commands:\r\n");
        Xport.print(" * 'D' : debug info\r\n");
        Xport.print(" * '?' : this menu\r\n");
        XportStatus = XPORT_INCOMING;
        break;
    }    
  }
  
  if(XportStatus == XPORT_RESET){
    xportClear();
    xportReset();
    xportClear();
    XportStatus = XPORT_IDLE;  
  }

}

void timer_expires()
{
  if(confirm_pulse){
    if((--confirm_pulse_count) == 0){
      if(digitalRead(GSC_INTERRUPT_PINNB)){
        PulseCount++;
      }
      confirm_pulse = false;
    }
  }
  
  led_count++;
    
  if(++SecondCount >= TIMER_IN_SECONDS){
    SecondCount = 0; 
    PulseCount_diff = PulseCount - PulseCount_prev;
    PulseCount_prev = PulseCount;
    add_to_day_count = true;
  }
  
  if(XportStatus == XPORT_OUTGOING){
    if(--OutgoingCount == 0){
      XportStatus = XPORT_RESET;
    }
  }
  if((XportStatus == XPORT_INCOMING)||(XportStatus == XPORT_WAIT_FOR_INCOMING)){
    if(--IncomingCount == 0){
      XportStatus = XPORT_RESET;
    }
  }
  
}


void gsc_count_debounce()
{
  static unsigned long last_interrupt_time = 0;
  unsigned long interrupt_time = millis();
  // If interrupts come faster than 200ms, assume it's a bounce and ignore
  if (interrupt_time - last_interrupt_time > 200)
  {
    confirm_pulse = true;
    confirm_pulse_count = PULSE_COUNT_CONFIRM;
  }
  last_interrupt_time = interrupt_time;
}


void print_menu(){
  Serial.println();
  Serial.println();
  Serial.println("embedded GSC counter");
  Serial.println("====================");
  Serial.println("\t'p' -> pulse count value");
  Serial.println("\t's' -> second count value");
#ifdef ENABLE_ETHERNET_MODE
  Serial.println("\t't' -> get time via internet");
#endif
  Serial.println("\t'h' -> help");
  Serial.println();
  Serial.print(" your choice: ");
}

void process_char(char resp){
  switch(resp){
    case 'p':
      Serial.println();
      Serial.print("pulse count value: ");
      Serial.println(PulseCount);
      break;
    case 's':
      Serial.println();
      Serial.print("second count value: ");
      Serial.println(SecondCount);
      break;
#ifdef ENABLE_ETHERNET_MODE
    case 't':
      Serial.println();
      Serial.print("time string: ");
      HttpMessage = HTTP_GET_TIME;
      HttpTransfer();
      break;
#endif
    case 'h':
      Serial.println();
      Serial.println("embedded GSC counter program (v0.1) written by Frederik Naessens");
      HttpMessage = HTTP_TEST_POST;
      HttpTransfer();
      break;
    default:
      Serial.print("Invalid choice");
  }
  print_menu();
}
